<template>
    <!-- #ifndef APP-NVUE -->
    <scroll-view 
        id="scrollView"
        class="lrl-virtual-list"
        :scroll-y="true"
        :lower-threshold="lowerThreshold"
        :upper-threshold="upperThreshold"
        :scroll-top="scrollTop"
        :scroll-with-animation="scrollWithAnimation"
        :enable-back-to-top="enableBackToTop"
        :refresher-enabled="refresherEnabled"
        :refresher-threshold="refresherThreshold"
        :refresher-default-style="refresherDefaultStyle"
        :refresher-background="refresherBackground"
        :refresher-triggered="refresherTriggered"
        :enable-flex="enableFlex"
        :scroll-anchoring="scrollAnchoring"
        @scroll="scrollViewEmit('scroll', $event);onScroll($event)"
        @click="scrollViewEmit('click', $event)"
        @scrolltoupper="scrollViewEmit('scrolltoupper', $event)"
        @scrolltolower="onScrolltolower($event);"
        @refresherpulling="onRefresherpulling($event), scrollViewEmit('refresherpulling', $event)"
        @refresherrefresh="onRefresherrefresh($event);scrollViewEmit('refresherrefresh', $event)"
        @refresherrestore="onRefresherrestore($event); scrollViewEmit('refresherrestore', $event)"
        @refresherabort="onRefresherabort($event);scrollViewEmit('refresherabort', $event)"
    >
        <view class="lrl-virtual-list__content">
            <!-- 虚拟列表原理：-->
            <!-- 1.用一个空view撑开list高度，使其可以滚动 -->
            <view class="lrl-virtual-list__replace" :style="[{ height: scrollHeight + 'px' }]"></view>
            <!-- 2.渲染内容通过滚动计算高度设置渲染内容以及垂直偏移量 -->
            <view 
                class="lrl-virtual-list__render" 
                :style="[
                    {
                        transform: `translateY(${ visibleTranslateY }px)`
                    }
                ]">
                <view 
                    v-for="(item, index) in actualVisibleData" 
                    :key="getCellKey(item, actualStartIndex + index)"
                    :id="'virtual_cell_' + actualStartIndex + index"
                    :class="{
                        'lrl-virtual-list__cell': true,
                        'measure': !visibleIndexs.includes(actualStartIndex + index)
                    }"
                >
                    <slot :itemData="item" :index="actualStartIndex + index"></slot>
                </view>
            </view>
        </view>
        
    </scroll-view>
    <!-- #endif -->
    <!-- #ifdef APP-NVUE -->
    <!-- app-nvue使用weex内置原生list组件，自带计算可视范围渲染，性能更好 -->
    <list 
        class="lrl-virtual-list"
    >
        <cell v-for="(item, index) in list" :key="getCellKey(index)">
            <slot :itemData="item" :index="index"></slot>
        </cell>
    </list>
    <!-- #endif -->
</template>

<!-- 微信小程序, QQ小程序, app, h5使用wxs -->
<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 -->
<script src="./wxs/wxs.wxs" module="wxs" lang="wxs"></script>
<!-- #endif -->

<!-- app, h5使用renderjs -->
<!-- #ifdef APP-VUE || H5 -->
<script module="renderJs" lang="renderjs">
    import renderJs from './wxs/renderjs.js';
    export default {
        mixin: [renderJs]
    }
</script>
<!-- #endif -->

<script>
    import { timeWait, getRect } from '../../libs/util/tools.js';
    import scrollViewNotHandleProps from './scrollViewNotHandleProps.js';
    import PrefixSumManage from './PrefixSumManage.js';
    import initArrayMethod from './initArrayMethod.js';
    import mixin from '../../libs/mixin/mixin.js';

    /**
     * VirtualList 虚拟列表
     * @description 虚拟列表，动态计算cell高度，只渲染可视范围数据，前端长列表顶级性能优化方案，轻松处理万级数据显示
     * @author liruilin
     * @tutorial 思路非原创，参照插件市场插件【xing-virtual-list】学习模仿制作，https://ext.dcloud.net.cn/plugin?id=7129
     * @property {Array}         list           数据列表源
     * @property {String}        cellKeyMap     列表循环key字段映射，例如设置为id，将会取list数据里的id字段当做key，默认为index索引
     * @property {Number}        preRender      预渲染数量，调整大小可以调节快速滑动白屏时间，app-nvue 无效
     * 
     */
    export default {
        name: 'lrl-virtual-list',
        mixins: [
            mixin,
            // scrollView 默认props以及事件中间处理
            scrollViewNotHandleProps
        ],
        props: {
            list: {
                type: Array,
                required: true,
                default: () => {
                    return []
                } 
            },
            // 预渲染数量，（前后）
            preRender: {
            	type: Number,
            	default: 1,
                validator: (value) => {
                    if(value < 1){
                        console.error('[lrl-virtual-list]：preRender 预渲染数量不能小于 1')
                    }
                	return value > 0;
                }
            },
            // 每次加载新数据时批次测量大小
            calculaSize: {
                type: Number,
                default: 5
            },
            scrollIntoView: {
                type: String,
                default: ''
            },
            cellKeyMap: {
                type: String,
                default: ''
            },
        },
        data(){
            return {
                // 记录最后一次滚动位置
                lastScrollTop: 0,
                // 前缀和高度记录
                prefixSumHeight: [],
                itemHeight: [],
                // 渲染开始位置
                startIndex: 0,
                // 渲染结束位置
                endIndex: 0,
                // 待测量数据
                measureData: [],
                // 滚动窗口高度
                scrollViewHeight: 0,
                // 
                refresheOffset: 0
            }
        },
        computed: {
            // 滚动内容总高度，用于占位
            scrollHeight(){
                // 前缀和最后一项即为高度
                return this.prefixSumHeight[this.prefixSumHeight.length - 1] || 0
            },
            // 可见数据
            visibleData(){
                return this.list.slice(this.actualStartIndex , this.actualEndIndex + 1 )
            },
            /**
             * 实际渲染： 预渲染 + 起点 + 终点 + 预渲染
             */
            // 实际渲染起点
            actualStartIndex(){
                return Math.max(this.startIndex - this.preRender + 1, 0)
            },
            // 实际渲染终点
            actualEndIndex(){
                return Math.min(this.endIndex + this.preRender, this.list.length - 1)
            },
            // 实际渲染数据
            actualVisibleData() {
            	// 实际渲染会加上待测量数据
            	return [...this.visibleData, ...this.measureData];
            },
            // 可见数据index索引 [10, .... ,20]
            visibleIndexs(){
                return this.visibleData.map((item, index) => this.actualStartIndex + index);
            },
            // 可视渲染区域垂直偏移量
            visibleTranslateY(){
                return this.prefixSumHeight[this.actualStartIndex - 1]
            },
            // 获取列表循环key
            getCellKey(){
                return (item, index) => {
                    return this.cellKeyMap ? item[this.cellKeyMap] : index
                }
            }
        },
        watch: {
            // scrollIntoView 需要内部自己实现
            scrollIntoView(id){
                
            },
            actualEndIndex(newIndex, oldIndex){

                if(newIndex > oldIndex && !this.prefixSumHeight[newIndex]){
                    let newStart = oldIndex + 1
                    let newItems = this.list.slice(newStart, newIndex + 1)
                    console.log('newItems')
                    console.log(newItems)
                    this.splice(newStart, newItems.length, newItems )
                }
                
            },
            list(n){
                console.log('list变化了')
                // if(this.prefixSumHeight.length > this.list.length){
                //     this.PrefixSumManage.splice(this.list.length, this.prefixSumHeight.length)
                // }
            }
        },
        created() {
            this.PrefixSumManage = new PrefixSumManage({
                target: this, 
                property: 'prefixSumHeight'
            })
            this.PrefixSumManage.$on('updateChange', this.prefixSumUpdateChange, true)
        },
        mounted() {
            this.init()
            console.log(this.list)
        },
    
        methods: {
            async init(){
                await this.queryScrollViewHeight()
                this.contentFullHandler()
            },
            // 判断内容是否沾满，未沾满添加数据
            async contentFullHandler(){
                console.log(this.scrollHeight < this.scrollViewHeight)
                if(this.scrollHeight < this.scrollViewHeight){
                    // this.splice(this.actualEndIndex, 1, this.list.slice(this.actualEndIndex, this.actualEndIndex + 1))ssss
                }
            },
            /**
             * 初始化包装list Array原型方法，监听改变
             */
            initListOrign(list){
                let methods = {
                	push(add) {
                        console.error('push')
                        
                	},
                	pop() {
                		
                	},
                	shift() {
                		
                	},
                	unshift() {
                		
                	},
                	sort() {
                		
                	},
                	reverse() {
                		
                	},
                	splice([index, leng, ...value]) {
                		console.log('splice', index, leng, value)
                        this.splice(index, leng, value)
                	},
                    concat() {
                        
                    }
                }
                initArrayMethod(list, methods, this)
            },
            /**
             * 监听列表滚动
             */
            onScroll(e){
                const { scrollTop } = e.detail
                this.lastScrollTop = scrollTop
                this.positionContentIndex(scrollTop)
                // console.log(this.actualEndIndex)
            },
            onScrolltolower($event){
                this.scrollViewEmit('scrolltolower', $event)
            },
            onRefresherpulling(e){
                // console.log(e)
                this.refresheOffset = e.detail.deltaY
            },
            onRefresherrefresh(e){
                // console.log(e)
                this.refresheOffset = 45
            },
            onRefresherrestore(e){
                // console.log(e)
                this.refresheOffset = 0
            },
            onRefresherabort(e){
                // console.log(e)
                this.refresheOffset = 0
            },
            consoleInfo(){
                console.log('start end', this.startIndex, this.endIndex) 
                console.log('start end actual', this.actualStartIndex, this.actualEndIndex) 
                console.log('lastScrollTop', this.lastScrollTop ) 
                console.log('+scrollViewHeight', this.lastScrollTop + this.scrollViewHeight) 
                console.log('prefixSumHeight',  this.prefixSumHeight)
                console.log('itemHeight',  this.itemHeight)
                console.log('list',  this.list.length)
            },
            /**
             * 定位可视数据位置
             * @param {Object} scrollTop
             */
            positionContentIndex(scrollTop){
                this.startIndex = this.PrefixSumManage.binarySearch(this.refresheOffset + scrollTop)
                this.endIndex = this.PrefixSumManage.binarySearch(this.refresheOffset + scrollTop + this.scrollViewHeight) + 1
                // console.log('重获位置')
                // console.log('start end', this.startIndex, this.endIndex) 
                // console.log(scrollTop, scrollTop + this.scrollViewHeight, this.actualStartIndex, this.actualEndIndex) 
                // console.log(this.prefixSumHeight, this.visibleData)
            },
            /**
             * 前缀和更新后触发
             */
            prefixSumUpdateChange(){
                console.log('完成', this.scrollHeight, this.scrollViewHeight)
                this.positionContentIndex(this.lastScrollTop)
                this.contentFullHandler()
            },
            /**
             * @param {Object} index
             * @param {Object} leng
             * @param {Object} measureData
             */
            async splice(index, leng, measureData){
                
                console.log('measureData', index, leng , measureData)
                this.measureData = measureData || []
                await this.$nextTick()
                let measureHeights = await this.getMeasureHeight()
                // setTimeout(() => {
                    this.measureData = []
                // }, 2000)
                console.log('measureHeights', measureHeights)
                this.PrefixSumManage.splice(index, leng, ...measureHeights)
                this.itemHeight.splice(index, leng, ...measureHeights)
            },
            // 获取预渲染各cell高度
            async getMeasureHeight(){
                const res = await getRect('.measure', this, true)
                return res.map(e => e.height)
            },
            // 查询滚动窗口高度
            async queryScrollViewHeight(){
               this.scrollViewHeight = (await getRect('#scrollView', this)).height
            }
        }
    }
</script>

<style lang="scss" scoped>
    .lrl-virtual-list{
        
        &__replace{
            background-color: blueviolet;
        }
        
        &__content{
            position: relative;
        }
        
        &__render{
            position: absolute;
            overflow: hidden;
            top: 0;
            left: 0;
            right: 0;
            background-color: rgba(150,150,150, 0.3);
        }
        
        &__cell{
            overflow: hidden;
        }
    }
</style>